package com.vincent.template.config;

import com.vincent.template.commons.base.BaseResultDto;
import com.vincent.template.commons.exception.Vct520Exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolationException;

/**
 * @Author Vincent Wang
 * @Date 2020/10/16 16:19
 */
@RestControllerAdvice
public class Vct520ExceptionHandler {

    private static final String SPLIT = "，";

    public static class ExceptionError {
        private String code;
        private String message;
        private String messageKey;

        public ExceptionError() {
            super();
        }

        public ExceptionError(String code, String message, String messageKey) {
            super();
            this.code = code;
            this.message = message;
            this.messageKey = messageKey;
        }

        public ExceptionError(String code, String message) {
            super();
            this.code = code;
            this.message = message;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public String getMessageKey() {
            return messageKey;
        }

        public void setMessageKey(String messageKey) {
            this.messageKey = messageKey;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

    }

    @ExceptionHandler(value = Vct520Exception.class)
    public ResponseEntity<ExceptionError> catchVct520Exception(Vct520Exception e) {
        Object[] args = e.getArgs();
        String message = e.getMessage();
        if (args != null && args.length > 0) {
            message = String.format(message, args);
        }
        ExceptionError errorInfo = new ExceptionError(e.getCode(), message, e.getMessageKey());
        return new ResponseEntity<>(errorInfo, HttpStatus.OK);
    }

    /**
     * validation校验产生的异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = {
            MethodArgumentNotValidException.class,
            ConstraintViolationException.class})
    public BaseResultDto<Object> validException(Throwable e) {
        if (e instanceof MethodArgumentNotValidException) {
            return BaseResultDto.fail(parseFieldValidMessage((MethodArgumentNotValidException) e));
        }
        return BaseResultDto.fail(parseParameterValidMessage((ConstraintViolationException) e));
    }

    /**
     * 请求方式错误、缺少query参数、缺少body
     * @param e
     * @return
     */
    @ExceptionHandler(value = {
            HttpRequestMethodNotSupportedException.class,
            MissingServletRequestParameterException.class,
            HttpMessageNotReadableException.class})
    public BaseResultDto<Object> methodNotSupportException(Throwable e) {
        String message = e.getMessage();
        if (e instanceof HttpMessageNotReadableException) {
            return BaseResultDto.fail(message.substring(0, message.indexOf(":")));
        }
        return BaseResultDto.fail(message);
    }

    /**
     * 解析MethodArgumentNotValidException的message（检验对象的属性）
     * @param exception
     * @return
     */
    private String parseFieldValidMessage(MethodArgumentNotValidException exception) {
        StringBuilder sb = new StringBuilder();
        exception.getBindingResult().getAllErrors().forEach(error -> {
            if (error instanceof FieldError) {
                sb.append(((FieldError) error).getField());
            }
            appendWithCheckingLanguage(sb, error.getDefaultMessage());
        });
        sb.deleteCharAt(sb.lastIndexOf(SPLIT));
        return String.valueOf(sb);
    }

    /**
     * 解析ConstraintViolationException的message（检验方法的形参）
     * @param exception
     * @return
     */
    private String parseParameterValidMessage(ConstraintViolationException exception) {
        StringBuilder sb = new StringBuilder();
        exception.getConstraintViolations().forEach(error -> {
            String propertyPath = error.getPropertyPath().toString();
            sb.append(propertyPath.substring(propertyPath.indexOf(".") + 1));
            appendWithCheckingLanguage(sb, error.getMessage());
        });
        sb.deleteCharAt(sb.lastIndexOf(SPLIT));
        return String.valueOf(sb);
    }

    /**
     * 追加信息并根据message中英文开头判断是否需要在字段名和message之间添加空格
     * @param sb
     * @param message
     */
    private void appendWithCheckingLanguage(StringBuilder sb, String message) {
        if (startsWithEnglish(message)) {
            sb.append(" ");
        }
        sb.append(message).append(SPLIT);
    }

    /**
     * 判断一个字符串的开头字母是否是英文
     * @param appendStr
     * @return
     */
    private boolean startsWithEnglish(String appendStr) {
        char startCh = appendStr.charAt(0);
        return startCh >= 'a' && startCh <= 'z' || startCh >= 'A' && startCh <= 'Z';
    }

}
